#pragma once
#include <string>
#include <sstream>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "log.hpp"
#include "Util.hpp"

#define VERSION "HTTP/1.0"
#define SPACE " "
#define SEG "\r\n"

#define OK 200
#define NOT_FIND 404

#define defaultSuffix ".html"

const std::string ROOT = "./wwwroot";
const std::string PAGE_404 = "404.html";

class httpRequest
{
public:
    std::string Line;
    std::vector<std::string> Header;
    std::string Body;

    std::string method;  // 请求方式
    std::string uri;     // 请求内容
    std::string version; // 版本

    std::string path;         // 请求目录
    std::string query_string; // post
    std::string suffix;       // 文件后缀

    std::unordered_map<std::string, std::string> HeaderMap;
};

class httpResponse
{
public:
    httpResponse() : cgi(false), status(OK), fd(-1)
    {
    }
    std::string Line;
    std::vector<std::string> Header;
    std::string Body;

    int fd;
    bool cgi;
    int status;
    int size;
};

class endPoint
{
private:
    int _sockfd;
    httpRequest _Request;
    httpResponse _Response;
    bool _stop;

    bool RecvRequestLine()
    {
        if(Util::ReadOneLine(_sockfd, _Request.Line) < 0)
        {
            Log(WARNING, "RecvRequestLine ReadOneLine warning");
            _stop = true;
        }
        return _stop;
    }
    bool RecvRequestHeader()
    {
        while (true)
        {
            std::string out;
            if(Util::ReadOneLine(_sockfd, out) < 0)
            {
                Log(WARNING, "RecvRequestHeader ReadOneLine warning");
                _stop = true;
                break;
            }
            Log(INFO, out.c_str());
            if (out.size() == 0)
                break;
            _Request.Header.push_back(out);
        }
        return _stop;
    }
    void ParseRequestLine()
    {
        std::stringstream ss(_Request.Line);
        ss >> _Request.method >> _Request.uri >> _Request.version;
        auto &method = _Request.method;
        std::transform(method.begin(), method.end(), method.begin(), ::toupper);
    }
    void ParseRequestHeader()
    {
        for (auto &head : _Request.Header)
        {
            size_t pos = head.find(": ");
            if (pos != std::string::npos)
            {
                std::string front = head.substr(0, pos);
                std::string later = head.substr(pos + 2);
                _Request.HeaderMap.insert({front, later});
            }
        }
    }

    /// @brief 检查http是否有内容
    /// @return 没有内容返回-1，有内容返回内容大小
    int IsNeedRecvRequestBody()
    {
        auto &method = _Request.method;
        if (method == "POST")
        {
            auto iter = _Request.HeaderMap.find("Content-Length");
            if (iter != _Request.HeaderMap.end())
            {
                int len = atoi(iter->second.c_str());
                return len;
            }
        }
        return -1;
    }
    void RecvRequestBody()
    {
        int n = IsNeedRecvRequestBody();
        Log(INFO, "Content-Length is : %d", n);
        if (n > 0)
        {
            char ch = 0;
            while (n)
            {
                int s = recv(_sockfd, &ch, 1, 0);
                if (s > 0)
                {
                    _Request.Body += ch;
                    n--;
                }
                else
                {
                    Log(WARNING, "RecvBody warning");
                    _stop = true;
                    break;
                }
            }
        }
    }
    void Debug()
    {
        // Log(INFO, _Request.Line.c_str());
        // for (auto &head : _Request.HeaderMap)
        // {
        //     Log(INFO, "%s|%s", head.first.c_str(), head.second.c_str());
        // }
        Log(INFO, _Request.Body.c_str());
    }
    std::string codeToString(int code)
    {
        std::string ret;
        switch (code)
        {
        case OK:
            ret = "OK";
            break;
        case NOT_FIND:
            ret = "Not Found";
            break;
        default:
            break;
        }
        return ret;
    }
    std::string suffixToString(std::string suffix)
    {
        static std::unordered_map<std::string, std::string> map = {
            {".html", "text/html"},
            {".css", "text/css"},
            {".js", "application/javascript"},
            {".jpg", "image/jpeg"},
        };
        auto iter = map.find(suffix);
        if (iter != map.end())
        {
            return iter->second;
        }
        return "text/html";
    }
    int ProcessNonCgi()
    {
        _Response.fd = open(_Request.path.c_str(), O_RDONLY);
        if (_Response.fd > 0)
        {
            return OK;
        }
        return NOT_FIND;
    }

    void addEnv()
    {
        setenv("METHOD", _Request.method.c_str(), 1);
        if (_Request.method == "GET")
        {
            setenv("QUERY", _Request.query_string.c_str(), 1);
        }
        else if (_Request.method == "POST") // POST 需要添加环境变量——body.size
        {
            setenv("POSTSIZE", std::to_string(_Request.Body.size()).c_str(), 1);
        }
    }

    int ProcessCgi()
    {
        Log(INFO, "Cgi running!");
        // 管道通讯
        int input[2]{0};
        int output[2]{0};
        if (pipe(input) < 0)
        {
            Log(WARNING, "pipe create fail");
            return NOT_FIND;
        }
        if (pipe(output) < 0)
        {
            Log(WARNING, "pipe create fail");
            return NOT_FIND;
        }

        pid_t pid = fork();
        if (pid == 0) // 子进程
        {
            close(input[0]);
            close(output[1]);

            addEnv();

            dup2(input[1], 1);
            dup2(output[0], 0);
            execl(_Request.path.c_str(), _Request.path.c_str(), nullptr);
            exit(1);
        }
        else if (pid > 0) // 父进程
        {
            close(input[1]);
            close(output[0]);

            // POST 通过管道传内容
            if (_Request.method == "POST")
            {
                const char *cPtr = _Request.Body.c_str();
                ssize_t capcity = _Request.Body.size();
                ssize_t size = 0;
                ssize_t total = 0;
                while (total < capcity)
                {
                    size = write(output[1], cPtr + total, capcity - total);
                    if (size <= 0)
                        break;
                    total += size;
                }
            }

            // GET POST的返回内容都通过管道读出

            char cbuf = 0;
            while (read(input[0], &cbuf, 1) > 0)
            {
                _Response.Body += cbuf;
            }
            Log(INFO, "the response body is : %s", _Response.Body.c_str());

            int status = 0;
            waitpid(pid, &status, 0);
            if (WIFEXITED(status))
            {
                if (WEXITSTATUS(status) == 0)
                {
                    return OK;
                }
                else
                {
                    Log(WARNING, "child exit, no is : %d", WEXITSTATUS(status));
                }
            }
            else
            {
                Log(WARNING, "child error");
            }
            return NOT_FIND;
        }
        else
        {
            Log(WARNING, "fork error");
            return NOT_FIND;
        }
        return OK;
    }

    void BuildOKResponse()
    {
        Log(INFO, "the page is : %s", _Request.path.c_str());
        std::string headerLine;
        headerLine += "Content-Length: ";
        if (_Response.cgi)
        {
            headerLine += std::to_string(_Response.Body.size());
        }
        else
        {
            headerLine += std::to_string(_Response.size);
        }
        headerLine += SEG;
        _Response.Header.push_back(headerLine);
        headerLine = "Content-Type: ";
        headerLine += suffixToString(_Request.suffix);
        headerLine += SEG;
        _Response.Header.push_back(headerLine);
    }

    void BuildErrorResponse(std::string errorPath)
    {
        _Response.cgi = false;
        _Response.fd = open(errorPath.c_str(), O_RDONLY);
        Log(INFO, "the page is : %s", errorPath.c_str());
        if (_Response.fd > 0)
        {
            struct stat statbuf;
            stat(errorPath.c_str(), &statbuf);
            _Response.size = statbuf.st_size;

            std::string headerLine;
            headerLine += "Content-Length: ";
            headerLine += std::to_string(_Response.size);
            headerLine += SEG;
            _Response.Header.push_back(headerLine);
            headerLine = "Content-Type: ";
            headerLine += "text/html";
            headerLine += SEG;
            _Response.Header.push_back(headerLine);
        }
        else
        {
            Log(ERROR, "Not find page : %s", errorPath.c_str());
        }
    }

    void BuildHttpResponseHelper()
    {
        auto &Line = _Response.Line;
        std::string errorPath = ROOT + "/";
        Line += VERSION;
        Line += SPACE;
        Line += std::to_string(_Response.status);
        Line += SPACE;
        Line += codeToString(_Response.status);
        Line += SEG;
        switch (_Response.status)
        {
        case OK:
            BuildOKResponse();
            break;
        case NOT_FIND:
            errorPath += PAGE_404;
            BuildErrorResponse(errorPath);
            break;
        default:
            break;
        }
    }

public:
    endPoint(int fd) : _sockfd(fd), _stop(false)
    {
    }
    ~endPoint()
    {
    }
    void RecvRequest()
    {
        if(!RecvRequestLine() && !RecvRequestHeader())
        {
            ParseRequestLine();
            ParseRequestHeader();
            RecvRequestBody();
        }
    }
    void BuildResponse()
    {
        auto &code = _Response.status;
        auto &cgi = _Response.cgi;
        auto &path = _Request.path;
        size_t found = 0;
        if (_Request.method != "POST" && _Request.method != "GET")
        {
            Log(WARNING, "The method is mistake");
            code = NOT_FIND;
            goto END;
        }
        if (_Request.method == "GET")
        {
            size_t pos = _Request.uri.find('?');
            if (pos != std::string::npos)
            {
                path = _Request.uri.substr(0, pos);
                _Request.query_string = _Request.uri.substr(pos + 1);
                cgi = true; // 此时请求报文在_Request.query_string中
            }
            else
            {
                path = _Request.uri;
            }
        }
        else if (_Request.method == "POST")
        {
            path = _Request.uri;
            cgi = true; // 此时请求报文在_Request.body中
        }
        if (path != "/")
            path = ROOT + path;
        else
            path = ROOT;
        struct stat filestat;
        if (stat(path.c_str(), &filestat) == 0) // 文件存在但有可能是目录文件
        {
            if (S_ISDIR(filestat.st_mode)) // 是目录文件
            {
                path += "/index.html";
                stat(path.c_str(), &filestat);
            }
            if ((filestat.st_mode & S_IXUSR) || (filestat.st_mode & S_IXGRP) || (filestat.st_mode & S_IXOTH))
            {
                cgi = true; // 是可执行文件
            }
            else
            {
                cgi = false;
            }
            _Response.size = filestat.st_size;
        }
        else
        {
            Log(WARNING, "The file[%s] is not find", path.c_str());
            code = NOT_FIND;
            goto END;
        }

        // 提取文件后缀
        found = path.rfind('.');
        if (found != std::string::npos)
        {
            _Request.suffix = path.substr(found);
        }
        else
        {
            _Request.suffix = defaultSuffix;
        }

        if (cgi)
        {
            code = ProcessCgi();
        }
        else
        {
            code = ProcessNonCgi();
        }
    END:
        // 集中进行响应报头构建，相应状态都在_Response.status
        BuildHttpResponseHelper();
    }
    void SendResponse()
    {
        send(_sockfd, _Response.Line.c_str(), _Response.Line.size(), 0);
        for (auto &head : _Response.Header)
        {
            send(_sockfd, head.c_str(), head.size(), 0);
        }
        send(_sockfd, SEG, 2, 0);
        if (_Response.cgi == true)
        {
            const char *cPtr = _Response.Body.c_str();
            ssize_t capcity = _Response.Body.size();
            ssize_t size = 0;
            ssize_t total = 0;
            while (total < capcity)
            {
                size = write(_sockfd, cPtr + total, capcity - total);
                if (size <= 0)
                    break;
                total += size;
            }
        }
        else
        {
            sendfile(_sockfd, _Response.fd, nullptr, _Response.size);
            close(_Response.fd);
        }
    }
    bool GetStop()
    {
        return _stop;
    }
};

class CallBack
{
public:
    CallBack()
    { }
    ~CallBack()
    { }
    void operator()(int fd)
    {
        HandlerRequest(fd);
    }
    void HandlerRequest(int fd)
    {
        Log(INFO, "accpet get fd : %d", fd);
        signal(SIGPIPE, SIG_IGN);
        endPoint *ep = new endPoint(fd);
        ep->RecvRequest();
        if(!ep->GetStop())
        {
            ep->BuildResponse();
            ep->SendResponse();
        }
        delete ep;

        close(fd);
        Log(INFO, "fd : %d close", fd);
    }
};
